﻿using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.Collections.Generic;
using System.IO;

namespace LowLevelGraphics.Filter
{
    /// <summary>
    /// Quadrilateral Distortion
    /// </summary>
    public class QuadrilateralDistortion : BaseImageFilter
    {
        protected double PIOver2 = ImageMath.HALF_PI; //Math.PI / 2.0;
                    //Find dimensions of new image
        protected Point[] m_aPointarray = new Point[] { new Point(0, 1), new Point(2, 0), new Point(5, 59), new Point(3, 3) };
        protected int m_nInterpolation = 1;

        /// <summary>
        /// Initializes a new instance of the <see cref="QuadrilateralDistortion"/> class.
        /// </summary>
        public QuadrilateralDistortion()
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="QuadrilateralDistortion"/> class.
        /// </summary>
        /// <param name="_quadrilateralDistortion">The _quadrilateral distortion.</param>
        internal QuadrilateralDistortion(QuadrilateralDistortion _quadrilateralDistortion)
        {
        }

        /// <summary>
        /// Distorts the specified source bitmap.
        /// </summary>
        /// <param name="topleft">The topleft.</param>
        /// <param name="topright">The topright.</param>
        /// <param name="bottomleft">The bottomleft.</param>
        /// <param name="bottomright">The bottomright.</param>
        /// <returns></returns>
        public QuadrilateralDistortion(Point topleft, Point topright, Point bottomleft, Point bottomright)
            : this(topleft, topright, bottomleft, bottomright, 2)
        {
        }

        /// <summary>
        /// Distorts the specified source bitmap.
        /// </summary>
        /// <param name="topleft">The topleft.</param>
        /// <param name="topright">The topright.</param>
        /// <param name="bottomleft">The bottomleft.</param>
        /// <param name="bottomright">The bottomright.</param>
        /// <param name="interpolation">The interpolation.</param>
        /// <returns></returns>
        public QuadrilateralDistortion(Point topleft, Point topright, Point bottomleft, Point bottomright, int interpolation)
        {
            m_aPointarray = new Point[] { topleft, topright, bottomright, bottomleft };
        }

        /// <summary>
        /// Execute this Filter
        /// A Debug Statement is written if a concrete Filter is called
        /// </summary>
        /// <param name="_bitmap"></param>
        /// <returns></returns>
        public override UnsafeBitmap Execute(UnsafeBitmap _bitmap)
        {
            double sourceWidth = _bitmap.Width;
            double sourceHeight = _bitmap.Height;

            int width = int.MinValue;
            int height = int.MinValue;

            foreach (Point p in m_aPointarray)
            {
                width = Math.Max(width, p.X);
                height = Math.Max(height, p.Y);
            }

            //if all points are 0, we will get an exception here...
            Bitmap bitmap = new Bitmap(width, height);

            //For faster image processing
            UnsafeBitmap newBmp = _bitmap;
            UnsafeBitmap sourceBmp = (UnsafeBitmap)_bitmap.Clone();

            //newBmp.LockImage();
            //sourceBmp.LockImage();

            //Key points
            PointF A = (PointF)m_aPointarray[0];
            PointF B = (PointF)m_aPointarray[1];
            PointF C = (PointF)m_aPointarray[2];
            PointF D = (PointF)m_aPointarray[3];

            // sides
            float mAB = ImageMath.GetAngle(A, B);
            float mCD = ImageMath.GetAngle(C, D);
            float mAD = ImageMath.GetAngle(A, D);
            float mBC = ImageMath.GetAngle(B, C);

            //Get corner intersections
            PointF O = ImageMath.GetIntersection(new ImageMath.Vector(B, mAB), new ImageMath.Vector(C, mCD));
            PointF N = ImageMath.GetIntersection(new ImageMath.Vector(A, mAD), new ImageMath.Vector(B, mBC));

            if (m_nInterpolation <= 0) m_nInterpolation = 1;
            int middleX = (int)(m_nInterpolation / 2.0);

            //Array of surronding pixels used for interpolation
            double[, ,] source = new double[m_nInterpolation, m_nInterpolation, 4];

            for (int y = 0; y < height; y++)
            {
                for (int x = 0; x < width; x++)
                {
                    //System.Windows.Forms.Application.DoEvents();

                    PointF P = new PointF(x, y);

                    float mPO = mAB; //Default value
                    float mPN = mBC;

                    if (O != PointF.Empty) //If intersection found, get coefficient
                        mPO = ImageMath.GetAngle(O, P);

                    if (N != PointF.Empty) //If intersection found, get coefficient
                        mPN = ImageMath.GetAngle(N, P);

                    //Get intersections
                    PointF L = ImageMath.GetIntersection(new ImageMath.Vector(P, mPO), new ImageMath.Vector(A, mAD));
                    if (L == PointF.Empty) L = A;

                    PointF M = ImageMath.GetIntersection(new ImageMath.Vector(P, mPO), new ImageMath.Vector(C, mBC));
                    if (M == PointF.Empty) M = C;

                    PointF J = ImageMath.GetIntersection(new ImageMath.Vector(P, mPN), new ImageMath.Vector(B, mAB));
                    if (J == PointF.Empty) J = B;

                    PointF K = ImageMath.GetIntersection(new ImageMath.Vector(P, mPN), new ImageMath.Vector(D, mCD));
                    if (K == PointF.Empty) K = D;

                    double dJP = ImageMath.GetDistance(J, P);
                    double dLP = ImageMath.GetDistance(L, P);

                    double dJK = ImageMath.GetDistance(J, K);
                    double dLM = ImageMath.GetDistance(L, M);

                    //set direction
                    if (dLM < ImageMath.GetDistance(M, P)) dLP = -dLP;
                    if (dJK < ImageMath.GetDistance(K, P)) dJP = -dJP;

                    ////interpolation

                    //find the pixels which surround the point
                    double yP0 = sourceHeight * dJP / dJK;
                    double xP0 = sourceWidth * dLP / dLM;

                    //top left coordinates of surrounding pixels
                    if (xP0 < 0) xP0--;
                    if (yP0 < 0) yP0--;

                    int left = (int)xP0;
                    int top = (int)yP0;

                    if ((left < -1 || left > sourceWidth) && (top < -1 || top > sourceHeight))
                    {
                        //if outside of source image just move on
                        continue;
                    }

                    //weights
                    double xFrac = xP0 - (double)left;
                    double xFracRec = 1.0 - xFrac;
                    double yFrac = yP0 - (double)top;
                    double yFracRec = 1.0 - yFrac;

                    //get source pixel colors, or white if out of range (to interpolate into the background color)
                    int x0;
                    int y0;
                    Color c;

                    for (int sx = 0; sx < m_nInterpolation; sx++)
                    {
                        for (int sy = 0; sy < m_nInterpolation; sy++)
                        {
                            x0 = left + sx;
                            y0 = top + sy;

                            if (x0 > 0 && y0 > 0 &&
                                x0 < sourceWidth && y0 < sourceHeight)
                            {
                                c = sourceBmp.GetPixel(x0, y0);

                                source[sx, sy, 0] = c.R;
                                source[sx, sy, 1] = c.G;
                                source[sx, sy, 2] = c.B;
                                source[sx, sy, 3] = 255.0f;
                            }
                            else
                            {
                                // set full transparency in this case
                                source[sx, sy, 0] = 0;
                                source[sx, sy, 1] = 0;
                                source[sx, sy, 2] = 0;
                                source[sx, sy, 3] = 0;
                            }
                        }
                    }

                    //interpolate on x
                    for (int sy = 0; sy < m_nInterpolation; sy++)
                    {
                        //check transparency
                        if (source[middleX, sy, 3] != 0 && source[0, sy, 3] == 0)
                        {
                            //copy colors from 1, sy
                            source[0, sy, 0] = source[1, sy, 0];
                            source[0, sy, 1] = source[1, sy, 1];
                            source[0, sy, 2] = source[1, sy, 2];
                            source[0, sy, 3] = source[1, sy, 3];
                        }
                        else
                        {
                            //compute colors by interpolation
                            source[0, sy, 0] = source[0, sy, 0] * xFracRec + source[middleX, sy, 0] * xFrac;
                            source[0, sy, 1] = source[0, sy, 1] * xFracRec + source[middleX, sy, 1] * xFrac;
                            source[0, sy, 2] = source[0, sy, 2] * xFracRec + source[middleX, sy, 2] * xFrac;
                            source[0, sy, 3] = source[0, sy, 3] * xFracRec + source[middleX, sy, 3] * xFrac;
                        }

                        //interpolate transparency
                        source[0, sy, 3] = source[0, sy, 3] * xFracRec + source[middleX, sy, 3] * xFrac;
                    }

                    //now interpolate on y

                    //check transparency
                    if (source[0, middleX, 3] != 0 && source[0, 0, 3] == 0)
                    {
                        //copy colors from 0, 1
                        source[0, 0, 0] = source[0, middleX, 0];
                        source[0, 0, 1] = source[0, middleX, 1];
                        source[0, 0, 2] = source[0, middleX, 2];
                        source[0, 0, 3] = source[0, middleX, 3];
                    }
                    else
                    {
                        source[0, 0, 0] = source[0, 0, 0] * yFracRec + source[0, middleX, 0] * yFrac;
                        source[0, 0, 1] = source[0, 0, 1] * yFracRec + source[0, middleX, 1] * yFrac;
                        source[0, 0, 2] = source[0, 0, 2] * yFracRec + source[0, middleX, 2] * yFrac;
                        source[0, 0, 3] = source[0, 0, 3] * yFracRec + source[0, middleX, 3] * yFrac;
                    }

                    //interpolate transparency
                    source[0, 0, 3] = source[0, 0, 3] * yFracRec + source[0, middleX, 3] * yFrac;

                    //store to bitmap
                    if (source[0, 0, 3] != 0) //pixel has color
                        newBmp.SetPixel(x, y, Color.FromArgb((int)source[0, 0, 3], (int)source[0, 0, 0], (int)source[0, 0, 1], (int)source[0, 0, 2]));
                }
            }

            return _bitmap;
        }

        /// <summary>
        /// This is used to clone a filter, each filter must implement it's own clone functionality
        /// </summary>
        /// <returns></returns>
        public override object Clone()
        {
            return new QuadrilateralDistortion(this);
        }
    }
}